/*global $, sap*/
sap.designstudio.sdk.Component.subclass("com.justbi.view.View", function() {
  
  // Properties
  var component = null;
  component = {
		  info : {		// component meta data
			  id : this.owner.sId,
			  name : "Just-BI View",
			  designMode :  ( (window.sap && sap.zen && sap.zen.designmode) !== undefined) ? true : false,
			  debugMode : false,
			  self : this,
			  root : 'zen/mimes/sdk_include/com.justbi.view/res',
			  buildFlag : true,
			  dataAvailable : false,
			  dependenciesAvailable : false
		  },
		  ds_props : {	// properties specified in the contribution XML
			  configString : "",
			  htmlString : "",
			  cssString : "",
			  jsString : "",
			  hidden : false
		  },
		  config : {	// configuration settings based on configString
			  dependencies : [],
			  data : []
		  }
  };
  
  
  /* Generate getter and setters for the design studio properties (component.ds_props) */
  for (var property in component.ds_props) {
	  if (component.ds_props.hasOwnProperty(property)) {
		  // skip the configString property, as this requires a special getter and setter
		  if(property === 'configString')
			  continue;
		  
		  // generate getter and setter functions
		  this[property] =
			  function(prop){
		        return function( value ) {
		      		if (value === undefined) {
		      		  if(!component.info.designMode && component.info.debugMode) 
		              console.log(component.info.name + "/" + component.info.id + ": Property Getter - " + prop);
		      		  
		      			return component.ds_props[prop];
		      		}
		      		if (!( component.ds_props[prop] === value )  ){
                if(!component.info.designMode && component.info.debugMode) 
                  console.log(component.info.name + "/" + component.info.id + ": Property Setter - " + prop + " value :" + value);
		      		  
		      			component.ds_props[prop] = value; 
		      			component.info.buildFlag = true;
		      			
		      			// set the hidden property also on the info level
		      			if(prop === "hidden"){
		      				component.info.isHidden = value;
		      			}
		      		}
		      		return this;
		        };
		      }(property);
	  }
  }

  
  /* Setup a special getter and setter for the configString in order to extract
   * the different types of configuration settings
   */
	this.configString = function(value) {
		if (value === undefined) {
      if(!component.info.designMode && component.info.debugMode) 
        console.log(component.info.name + "/" + component.info.id + ": Property Getter - configString");
		  
		  return component.ds_props.configString;
		}

		// check if the configString has changed
		if(component.ds_props.configString !== value ){
			component.info.buildFlag = true;
			
      if(!component.info.designMode && component.info.debugMode) 
        console.log(component.info.name + "/" + component.info.id + ": Property Setter - configString value :" + value);
			
		  // extract the different types of configuration
			try{
				if(value !== ""){
					var configObject = JSON.parse( value );
					component.config.data = (configObject.data !== undefined && configObject.data !== null && configObject.data !== "") ? configObject.data : [];
					
					// check if the libraryConfig has changed --> if so, remove the loadedLibraries from the data attribute
					var dependencyConfig = (configObject.dependencies !== undefined && configObject.dependencies !== null && configObject.dependencies !== "") ? configObject.dependencies : [];
					if ( (JSON.stringify( component.config.dependencies ) ) !== ( JSON.stringify(dependencyConfig) ) ){
						$("#" + component.info.id).removeData('dependenciesLoaded');
						component.config.dependencies = dependencyConfig;
			        }
					
					// add the defined properties to the props object
					component.ds_props.configString = value;
				}
			}catch(error){
				alert('Error parsing the configuration string: ' + error.message);
			}
		}
		return this;
	};
	
      

  /****************************************************************************
   * 	DESIGN STUDIO EVENTS
   ****************************************************************************/

  /**
   * Design Studio Event: Initialization
   * This event triggered by Design Studio when the component is added to the
   * canvas (or in Design Studio itself when moving the component around.
   */
  this.init = function() { 
    
    if(!component.info.designMode && component.info.debugMode) 
      console.log(component.info.name + "/" + component.info.id + ": Starting INIT EVENT");
    
	  // add an event listener for building the component
	  $("#" + component.info.id).unbind("buildComponent");
	  $("#" + component.info.id).on("buildComponent", function(event){
	    if(!component.info.designMode && component.info.debugMode) 
	      console.log(component.info.name + "/" + component.info.id + ": Event Triggered : buildComponent()");
	    
		  buildComponent();
	  });
	  
	  // add a generic event handler (which triggers an event in Design Studio)
	  $("#" + component.info.id).unbind("DesignStudioEvent");
		$("#" + component.info.id).on("DesignStudioEvent", function(event, eventPropertyObject) {
      if(!component.info.designMode && component.info.debugMode) 
        console.log(component.info.name + "/" + component.info.id + ": Event Triggered : DesignStudioEvent()");
		  
			setProperties(eventPropertyObject);
			component.info.self.fireEvent("CustomEvent");
		});
		
		component.info.buildFlag = true;
  };

  /**
   * Design Studio Event: AfterUpdate
   * This event triggered by Design Studio when the properties of the component
   * are updated.
   */
  this.afterUpdate = function() {
    if(!component.info.designMode && component.info.debugMode) 
      console.log(component.info.name + "/" + component.info.id + ": Starting AFTERUPDATE EVENT");
    
    
	  // in design studio the component may be rebuild after every call of
	  // this function. However, during runtime this must be specified by
	  // the getter/setter. This in order to prevent the rebuild of the 
	  // component too many times
	  if(component.info.designMode){
		  component.info.buildFlag = true;
		  component.info.dataAvailable = false;
		  component.info.dependenciesAvailable = false;
	  }
	  
	  // check if the component needs to be build
	  if(!component.info.buildFlag){
		  return
	  }
	  component.info.buildFlag = false;
	
	  
	  // check if the required data is loaded
	  if(requiredDataAvailable()){
		  component.info.dataAvailable = true;
	  }
	  
	  // load the dependencies 
	  if(component.info.dependenciesAvailable){
	    buildComponent();
	  }else{
	    getDependencies();
	  }
	  
  };
  
  

  /****************************************************************************
   *  HELPER FUNCTIONS
   ****************************************************************************/

  function buildComponent(){
    if(!component.info.designMode && component.info.debugMode) 
      console.log(component.info.name + "/" + component.info.id + ": Function buildComponent() started");
    
    // data loaded?
    if(!component.info.dataAvailable){
      if(!component.info.designMode && component.info.debugMode) 
        console.log(component.info.name + "/" + component.info.id + ": Function buildComponent() stopped --> No data available");
      
      return;
    }
    
    // dependencies available?
    if(!component.info.dependenciesAvailable){
      if(!component.info.designMode && component.info.debugMode) 
        console.log(component.info.name + "/" + component.info.id + ": Function buildComponent() stopped --> Dependencies not available");
      
      return;
    }
    
	  // If no HTML or CSS is provided, display an area in design studio (thus
	  // design time only) to show where the view component will be displayed
	  if(component.info.designMode && component.ds_props.htmlString === "" && component.ds_props.cssString === "" && component.ds_props.jsString === "" && component.ds_props.configString === ""){
		  $("#" + component.info.id).html( "<div style='display:table;width:100%;height:100%;border-style:solid;border-width:1px;border-color:grey;background-color:lightgrey;'><div style='display:table-cell;vertical-align:middle;text-align:center'>Unconfigured<br>" + component.info.name + "</div></div>" );
	  }else{
		  addCSS( component.ds_props.cssString );
		  $("#" + component.info.id).html( component.ds_props.htmlString );
	  }
	  
	  // add the Javascript
	  try{
		  eval( component.ds_props.jsString );
	  }catch(error){
		  alert(error.message);
	  }
	  
	  // hide the component if required
	  if(component.ds_props.hidden){
		  $("#"+component.info.id).hide();
	  }else{
		  $("#"+component.info.id).show();
	  }
	  
  }
  
  
  	function getDependencies(){
	  
      if(!component.info.designMode && component.info.debugMode) 
        console.log(component.info.name + "/" + component.info.id + ": Function getDependencies() started");
  	  
  		var dependenciesReadyForLoad = [];
  		
  		// if the dependency check was already done, or if no dependencies are specified, the component can directly be build
  		var dependenciesLoaded = $("#" + component.info.id).data("dependenciesLoaded");
  		if(dependenciesLoaded || (component.config.dependencies === undefined || component.config.dependencies.length === 0 ) ){
  		  if(!component.info.dependenciesAvailable){
  		    component.info.dependenciesAvailable = true; 
  		    
  	      if(!component.info.designMode && component.info.debugMode) 
  	        console.log(component.info.name + "/" + component.info.id + ": Triggering Event buildComponent from function getDependencies() --> all dependencies are loaded");
  		    
  		    $("#" + component.info.id).trigger("buildComponent");
          return;
  		  }
  		  return;
  		}
  		

		  // get the dependencies already loaded
		  var loadedDependencies = ( $("body").data("customResources") !== undefined ) ?  $("body").data("customResources") :  [];
		  var newDependenciesLoaded = [];
		  
		 // collect the libraries that can be loaded
  		for(var i=0;i<component.config.dependencies.length;i++){
        if(!component.info.designMode && component.info.debugMode) 
          console.log(component.info.name + "/" + component.info.id + ": Looping dependencies: counter --> " + i);
		  
		  	// check if the mandatory parameters are available
		  	if( component.config.dependencies[i].id === undefined || component.config.dependencies[i].id === "" || component.config.dependencies[i].url === undefined || component.config.dependencies[i].url === "" ){
			  	continue;
		  	}
		  
		  	// check if the dependency was already loaded
		  	if(loadedDependencies.indexOf( component.config.dependencies[i].id ) > -1 ){
			  	continue;
		  	}
		  
		  	// check if the dependency has other dependencies
		  	if(component.config.dependencies[i].dependencies !== undefined && component.config.dependencies[i].dependencies.length > 0){
			  
			  	// check if all the dependencies for this dependency have been loaded already
			  	var allDependenciesDependenciesLoaded = true;
			  	for(var y=0;y<component.config.dependencies[i].dependencies.length;y++){
		        if(!component.info.designMode && component.info.debugMode) 
		          console.log(component.info.name + "/" + component.info.id + ": Looping dependencies dependencies: counter --> " + y);
			  	  
				  	if(loadedDependencies.indexOf( component.config.dependencies[i].dependencies[y] ) === -1 ){
					  	allDependenciesDependenciesLoaded = false;
					  	break;
				  	}
			  	}
			  	if(!allDependenciesDependenciesLoaded){
				  	continue;
			  	}
		  	}
		  	
		  	// add the dependency to a collection of resources to be loaded by head.js
        if(!component.info.designMode && component.info.debugMode) 
          console.log(component.info.name + "/" + component.info.id + ": Adding dependency " + component.config.dependencies[i].id) + " to array for loading";
		  	
  			var lib = {};
  			lib[component.config.dependencies[i].id] = component.config.dependencies[i].url.replace("{root}", component.info.root);
  			dependenciesReadyForLoad.push(lib);
  			
  			// add the id of the dependency to an array for updating the already loaded dependencies
  			newDependenciesLoaded.push(component.config.dependencies[i].id);
		  }
  		
  		
  		// if the array holding the dependencies to be loaded is empty, this means that either
  		// all dependencies are loaded or that the dependencies of the dependencies were never
  		// loaded (which means that the configuration is not correct). In both cases the component
  		// can just be build.
  		if(dependenciesReadyForLoad.length === 0){
  		  if(!component.info.dependenciesAvailable){
  		    $("#" + component.info.id).data("dependenciesLoaded", true);
  			  component.info.dependenciesAvailable = true;
  			  
  			  if(!component.info.designMode && component.info.debugMode) 
            console.log(component.info.name + "/" + component.info.id + ": Triggering Event buildComponent from function getDependencies() --> no dependencies to be loaded");
          
  			  $("#" + component.info.id).trigger("buildComponent");
  			  return;
  		  }
  		  return;
  		}
  		
  		// In case there are dependencies to be loaded, use the head JS library to make sure
  		// that we only continue trying to load other dependencies once we are sure these
  		// dependencies are completely loaded.
		head.load( dependenciesReadyForLoad, function(){

		  if(!component.info.designMode && component.info.debugMode) 
		    console.log(component.info.name + "/" + component.info.id + ": head.load callback function started");
		  
			// update the loaded dependency identifiers to the body for other components
			loadedDependencies = loadedDependencies.concat( newDependenciesLoaded );
			$("body").data("customResources", loadedDependencies);
			
			
			// Reload this function until all dependencies are loaded
			if(!component.info.designMode && component.info.debugMode) 
        console.log(component.info.name + "/" + component.info.id + ": head.load callback function triggering function getDependencies()");
			
			getDependencies();
		});
  }

  
  function requiredDataAvailable(){
	    
    if(!component.info.designMode && component.info.debugMode) 
      console.log(component.info.name + "/" + component.info.id + ": Function requiredDataAvailable() started");
    
	  // check the availability
	  if(checkAvailabilityRequiredData()){
		  $('#' + component.info.id).data('dataloaded', true);
		  return true;
	  }
	  $('#' + component.info.id).data('dataloaded', false);
	  if(component.info.designMode){
		  $('#' + component.info.id).html("<div style='display:table;width:100%;height:100%'><div style='width:100%;height:100%;background-color:lightgrey;display:table-cell;vertical-align:middle;text-align:center;font-size:10px;font-family:Verdana'><b>" + component.info.name + "</b><br>Data not available.</div></div>");
	  }
	  return false;
	  
  }
  
  
  function checkAvailabilityRequiredData(){
	  
    if(!component.info.designMode && component.info.debugMode) 
      console.log(component.info.name + "/" + component.info.id + ": Function checkAvailabilityRequiredData() started");
    
	  // get the required and the available resources
	  if(component.config.data !== undefined && component.config.data.length > 0){
		  var availableData = ( $("body").data("customData") !== undefined ) ?  $("body").data("customData") :  [];
		  
		  // check if all required libs are available
		  for(var i=0;i<component.config.data.length;i++){
			  
		    if(!component.info.designMode && component.info.debugMode) 
		      console.log(component.info.name + "/" + component.info.id + ": Function checkAvailabilityRequiredData() adding eventlistener for id 'dataTrigger" + component.config.data[i] +"'");
		    
			  // add an event listener for the data
			  $("#" + component.info.id).unbind('dataTrigger' + component.config.data[i]);
			  $("#" + component.info.id).on('dataTrigger' + component.config.data[i], function(){
			    if(!component.info.designMode && component.info.debugMode) 
			      console.log(component.info.name + "/" + component.info.id + ": Event Triggered : dataTrigger" + component.config.data[i]);
			    
			  	if( checkAvailabilityRequiredData() ){
			   		component.info.buildFlag = true;
			   		component.info.dataAvailable = true;
			   		if(component.info.dependenciesAvailable){
			   		 if(!component.info.designMode && component.info.debugMode) 
		            console.log(component.info.name + "/" + component.info.id + ": Triggering function buildComponent() from event dataTrigger" + component.config.data[i]);
			   		  
			   		  buildComponent();
			   		}else{
	             if(!component.info.designMode && component.info.debugMode) 
	                console.log(component.info.name + "/" + component.info.id + ": Triggering function getDependencies() from event dataTrigger" + component.config.data[i]);
			   		  
			   		 getDependencies();
			   		}
			    }
			  });
			  
			  // check if the data is available
			  if(component.config.data[i] !== "" && availableData[component.config.data[i]] === undefined){
				  return false;
			  }
		  }
	  }
	  	  return true;
  }
  

  
  /**
   * All custom properties are stored via a single JSON string. In order to retrieve
   * the values this function is used. This function can also be used from within
   * the JavaScript section in the additional properties.
   *
   * @param {String} propertyName
   * 		  Name of the property
   * @returns {Object} returnValue
   *          Value of the requested property
   */
  function getProperty(propertyName){
    if(!component.info.designMode && component.info.debugMode) 
      console.log(component.info.name + "/" + component.info.id + ": Function getProperty() started");
    
    if (component.ds_props.configString !== undefined && component.ds_props.configString !== ""){
    	try{
    	  var configObject = JSON.parse( component.ds_props.configString );
    	  
    	  // recursive function to find property in nested object
    	  function getPropertyValue(propertyObject, propName){
    	    var propertyValue = null;
    	    for (var property in propertyObject) {
    	        if (propertyObject.hasOwnProperty(property)) {
    	            if(property === propName){
    	               return propertyObject[propName];
    	            }

    	            if(typeof propertyObject[property] === "object"){
    	               propertyValue = getPropertyValue(
    	                                   propertyObject[property], 
    	                                   propName
    	                               );
    	               if(propertyValue !== null && propertyValue !== undefined){
    	                  return propertyValue;
    	               }
    	            }
    	        }
    	    }
    	    return propertyValue;
    	  }
    	  
    	  return getPropertyValue(configObject.propertyInterface.data, propertyName);
    	  
    		
    	}catch(error){
    		return null;
    	}
    } 
    return null;
  }
  
  
  /**
   * Retrieve all the custom defined properties
   *
   * @returns {Object} properties
   *          Object holding all the properties
   */
  function getProperties(){
    if(!component.info.designMode && component.info.debugMode) 
      console.log(component.info.name + "/" + component.info.id + ": Function getProperties() started");
    
    if (component.ds_props.configString !== undefined && component.ds_props.configString !== ""){
    	try{
    		var configObject = JSON.parse( component.ds_props.configString );
    	    return configObject.propertyInterface.data; 
    	}catch(error){
    		return null;
    	}
    } 
    return null;
  }
  
  
  /**
   * All custom properties are stored via a single JSON string. In order to set
   * the values via JavaScript, this function is used.
   *
   * @param {String} propertyName
   *          Property Name
   * @param {String} propertyValue
   * 		  Value to set      
   */
  function setProperty(propertyName, propertyValue, optTriggerDSEvent){
    if ('undefined' === typeof optTriggerDSEvent) {
      optTriggerDSEvent = true;
    }
    
    if(!component.info.designMode && component.info.debugMode) 
      console.log(component.info.name + "/" + component.info.id + ": Function setProperty() started");
    
	  var configObject = {};
  
	  // parse the JSON string
	  if (component.ds_props.configString !== undefined && component.ds_props.configString !== ""){
		  configObject = JSON.parse( component.ds_props.configString); 
	  }
    
	  // check if the propertyInterface exists
	  if(configObject.propertyInterface === undefined){
	    configObject.propertyInterface = {};
	    configObject.propertyInterface.data = {};
	  }
	  
	  // check if the data object exists
	  if(configObject.propertyInterface.data === undefined){
	    configObject.propertyInterface.data = {};
	  }
	  
	  // set the value	  
	  //configObject.propertyInterface.data[propertyName] = propertyValue;
	  try{
      
      // recursive function to find property in nested object
      function setPropertyValue(propertyObject, propName, propValue){
        for (var property in propertyObject) {
            if (propertyObject.hasOwnProperty(property)) {
                if(property === propName){
                   propertyObject[propName] = propValue;
                   return true;
                }

                if(typeof propertyObject[property] === "object"){
                   if(setPropertyValue(
                                       propertyObject[property], 
                                       propName,
                                       propValue
                                   )){
                     return true;
                   }
                }
            }
        }
        return false;
      }
      
      if(setPropertyValue(configObject.propertyInterface.data, propertyName, propertyValue) === false){
        configObject.propertyInterface.data[propertyName] = propertyValue;
      }
            
    }catch(error){
      configObject.propertyInterface.data[propertyName] = propertyValue;
    }
	  

	  // acknowledge the design studio server that the property has changed
    if(typeof optTriggerDSEvent === undefined || optTriggerDSEvent === true){
  	  if(component.ds_props.configString !== JSON.stringify( configObject )){
  		  component.ds_props.configString = JSON.stringify( configObject );
  		  component.info.self.firePropertiesChanged(["configString"]);  
  	  }
    }
	  
  }
  
  
  /**
   * All custom properties are stored via a single JSON string. In order to set
   * the values via JavaScript, this function is used.
   *
   * @param {Array} propertiesObject
   *          Array holding properties    
   */
  function setProperties(propertiesObject){
    if(!component.info.designMode && component.info.debugMode) 
      console.log(component.info.name + "/" + component.info.id + ": Function setProperties() started");
    
	  var configObject = {},
      	isEmptyObject = true;
    
	  // check if the current property string is used
     if (component.ds_props.configString !== undefined && component.ds_props.configString !== ""){
       configObject = JSON.parse(component.ds_props.configString);
       isEmptyObject = jQuery.isEmptyObject(configObject.propertyInterface);
     }

     // if the current property string is used, extend it with the new properties
     if (isEmptyObject === false){
       if(configObject.propertyInterface.data !== undefined){
         configObject.propertyInterface.data = $.extend(true, configObject.propertyInterface.data, propertiesObject);
       }else{
         configObject.propertyInterface.data = propertiesObject;
       }
    	 
     } else {
    	 configObject.propertyInterface = {
    	     data : propertiesObject
    	 };
     }
     
     // acknowledge the server of the update
     component.ds_props.configString = JSON.stringify(configObject);
     component.info.self.firePropertiesChanged(["configString"]);
  }

  
  /**
   * Add the CSS provided via the component to the header of the document
   *
   * @param {String} css String
   *          String holding the CSS    
   */
  function addCSS(css){
    if(!component.info.designMode && component.info.debugMode) 
      console.log(component.info.name + "/" + component.info.id + ": Function addCSS() started");
    
	  var head = document.getElementsByTagName('head')[0];
	  var s = document.createElement('style');
	  s.setAttribute('type', 'text/css');
	  if (s.styleSheet) {   // IE
		  s.styleSheet.cssText = css;
	  } else {                // the world
	  	s.appendChild(document.createTextNode(css));
	  }
	  head.appendChild(s);
  }

});